package com.wl.api.filter.autentication;

import java.io.Closeable;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Objects;

import javax.servlet.DispatcherType;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.httpclient.HttpStatus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.wl.api.common.annotation.RequiresAuthentication;
import com.wl.api.common.utils.CollectionUtil;
import com.wl.api.common.utils.ErrorCodeEnum;
import com.wl.api.common.utils.ObjectUtil;
import com.wl.api.common.utils.RWYException;
import com.wl.api.common.utils.StringUtil;
import com.wl.api.common.utils.WebUtil;
import com.wl.api.common.utils.constant.AppEnum;
import com.wl.api.common.utils.constant.AppTokenEnum;
import com.wl.api.common.utils.constant.ClientConstant;
import com.wl.api.dto.base.CommonUserDTO;

import lombok.extern.log4j.Log4j;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;

@Log4j
@Component
public class UserAuthorizeInterceptor extends HandlerInterceptorAdapter {

	private static String openid_key = AppTokenEnum.common_token_key.getToken_key();
	@Autowired
	private CacheManager cacherManager;

	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
		boolean pass = false;
		int status = HttpStatus.SC_UNAUTHORIZED;
		if (DispatcherType.ERROR.equals(request.getDispatcherType())) {
			return true;
		}

		if (!(handler instanceof HandlerMethod)) {
			return super.preHandle(request, response, handler);
		}

		HandlerMethod handlerMethod = (HandlerMethod) handler;
		String access_token = request.getParameter(openid_key);
		try {
			pass = validateClassAnnotation(handlerMethod, access_token);
			if (!pass) {
				pass = validateMethodAnnotation(handlerMethod.getMethodAnnotation(RequiresAuthentication.class), access_token);
			}
		} catch (Exception e) {
			if (e instanceof RWYException.AuthorizeException){
				status = HttpStatus.SC_UNAUTHORIZED;
			}else if (e instanceof RWYException.ForbiddenException) {
				status = HttpStatus.SC_FORBIDDEN;
			}else {
				status = HttpStatus.SC_BAD_REQUEST;
			}
			log.error(e.getStackTrace());
		}

		if (!pass) {
			faileResponse(response, request,  HttpStatus.getStatusText(status), status);
			return false;
		}
		return true;
	}

	/**
	 * 
	 * @author chenj
	 * @datetime 2016年7月1日 下午5:49:00
	 * @desc 验证整个class 是否放行 , 不通过直接false ,进入验证方法如再不通过直接throw exception401
	 * @param classAnnotation
	 * @return
	 */
	private boolean validateClassAnnotation(HandlerMethod handlerMethod, String access_token) throws Exception {
		boolean pass = false;
		Object data = handlerMethod.getBean();
		Class<?> cladata = data.getClass();

		RequiresAuthentication classFilter = (RequiresAuthentication) cladata.getAnnotation(RequiresAuthentication.class);
		CommonUserDTO user = getUserByToken(access_token);

		if (classFilter == null && Objects.isNull(user)) {
			return false;
			//throw RWYException.create(401);
		}

		AppEnum app = classFilter == null ? null: classFilter.getClientType();
		
		//** 如果类controller 没有标记权限注解, 则直接跳过类验证进入方法验证*/
		if (app == null) {
			return false;
		}
		
		/**1. 匿名方法直接通过
		   //已改: 2.没有注解方法,只要用户不为空则可以访问(该放行只正对方法)
		   */
		if (app != null && app == AppEnum.anonymity_client) {
			pass = true;
		}
		
		if (!pass && Objects.isNull(user)) {
			throw RWYException.create(401);
		}
		

		if (!pass && app == AppEnum.customer_client) {
			if (!Objects.equals(user.getClientId(), ClientConstant.CUSTOMER_CLIENT_ID)) {
				throw RWYException.create(403);
			}
			pass = true;
		}

		String sn = classFilter.getAuthticationSn();
		if (!pass && app == AppEnum.admin_client) {
			if (!Objects.equals(user.getClientId(), ClientConstant.ADMIN_CLIENT_ID) 
					|| StringUtil.isEmpty(sn) 
					|| CollectionUtil.isEmpty(user.getSnList()) || !user.getSnList().contains(sn)) {
				throw RWYException.create(403);
			}
			pass = true;
		}
		return pass;
	}

	/**
	 * 
	 * @author chenj
	 * @datetime 2016年7月1日 下午5:51:33
	 * @desc 验证方法是否放行
	 * @return
	 */
	private boolean validateMethodAnnotation(RequiresAuthentication methodFilter, String access_token) throws Exception {
		boolean pass = false;

		CommonUserDTO user = getUserByToken(access_token);

		if (methodFilter == null && Objects.isNull(user)) {
			throw RWYException.create(401);
		}

		AppEnum app = methodFilter == null ? null : methodFilter.getClientType();
		/**1. 匿名方法直接通过
		   2.没有注解方法,只要用户不为空则可以访问
		   */
		if ((app != null && app == AppEnum.anonymity_client) || (app == null && Objects.nonNull(user))) {
			pass = true;
		}
		
		if (!pass && Objects.isNull(user)) {
			throw RWYException.create(401);
		}

		if (!pass && AppEnum.customer_client == app) {
			if (!Objects.equals(user.getClientId(), ClientConstant.CUSTOMER_CLIENT_ID)) {
				throw RWYException.create(403);
			}
			pass = true;
		}

		String sn = Objects.isNull(methodFilter) ? "": methodFilter.getAuthticationSn();
		if (!pass && AppEnum.admin_client == app) {
			if (!Objects.equals(user.getClientId(), ClientConstant.ADMIN_CLIENT_ID) || StringUtil.isEmpty(sn) || CollectionUtil.isEmpty(user.getSnList()) || !user.getSnList().contains(sn)) {
				throw RWYException.create(403);
			}
			pass = true;
		}
		return pass;
	}

	@Override
	public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
		try {
			int status = 400;
			if (!ObjectUtil.isEmpty(ex)) {
				AuthorizeStatus json = new AuthorizeStatus();
				if (ex instanceof RWYException.AuthorizeException) {
					json.setErrorCode(ErrorCodeEnum.SC_UNAUTHORIZED_code);
					json.setMsg(ErrorCodeEnum.SC_UNAUTHORIZED_message);
					status = HttpStatus.SC_UNAUTHORIZED;
				} else if (ex instanceof RWYException.ErrorException) {
					json.setErrorCode("000405");
					json.setMsg("request error !");
					status = HttpStatus.SC_BAD_REQUEST;
				}else if (ex instanceof RWYException.ForbiddenException) {
					json.setErrorCode(ErrorCodeEnum.sc_login_forbidden_code);
					json.setMsg(ErrorCodeEnum.sc_login_forbidden_message);
					status = HttpStatus.SC_FORBIDDEN;
				} 
				else {
					json.setErrorCode("000500");
					json.setMsg("request error, plase contact us ! ");
					status = HttpStatus.SC_INTERNAL_SERVER_ERROR;
				}
				log.error(ex.getMessage());
				faileResponse(response, request, json, status);
				ex = null;
			}
		} catch (Exception e) {
			log.error(e.getMessage());
		}
	}

	/**
	 * oAuth认证失败时的输出
	 * 
	 * @param res
	 * @throws OAuthSystemException
	 * @throws IOException
	 */
	private void faileResponse(HttpServletResponse res, HttpServletRequest req, String errorMsg, Integer status) throws Exception {
		res.setCharacterEncoding("UTF-8");
		res.setContentType("application/json; charset=utf-8");
		res.setStatus(status);
		PrintWriter writer = res.getWriter();
		writer.write(authorizeErrorMesage(status, req.getRequestURI()));
		writer.flush();
		writer.close();
		log.error("error:  error msg " + errorMsg);
		log.error(" error: user authorize mesage response data:" + authorizeErrorMesage(status, req.getRequestURI(), WebUtil.getParameters(req).toString()));

	}

	private void faileResponse(HttpServletResponse res, HttpServletRequest req, AuthorizeStatus json, Integer status) throws Exception {
		Gson gson = new GsonBuilder().create();
		res.reset();
		res.setCharacterEncoding("UTF-8");
		res.setContentType("application/json; charset=utf-8");
		res.setStatus(status);
		PrintWriter writer = res.getWriter();
		writer.write(gson.toJson(json));
		writer.flush();
		writer.close();
		log.error(" error: user authorize mesage response data:" + authorizeErrorMesage(status, req.getRequestURI(), WebUtil.getParameters(req).toString()));

	}

	private CommonUserDTO getUserByToken(String access_token) {
		if (StringUtil.isEmpty(access_token)) {
			return null;
		}
		Element obj = cacherManager.getCache("access_token").get(access_token);
		return Objects.isNull(obj) ? null : (CommonUserDTO) obj.getObjectValue();
	}

	/*
	 * private Element getUser(String openid) { if (StringUtil.isEmpty(openid))
	 * return null; Element user = cacherManager.getCache("users").get(openid);
	 * return user; }
	 */

	public void close(Closeable c) {
		if (c == null)
			return;
		try {
			c.close();
		} catch (IOException e) {
			//
		}
	}

	private String authorizeErrorMesage(Integer code, String request) {
		AuthorizeStatus msg = new AuthorizeStatus("" + code, request);
		Gson gson = new GsonBuilder().create();
		return gson.toJson(msg);
	}
	private String authorizeErrorMesage(Integer code, String request, String msg) {
		AuthorizeStatus data = new AuthorizeStatus("" + code, request, msg);
		Gson gson = new GsonBuilder().create();
		return gson.toJson(data);
	}

	/*
	 * public String getRedicect_url() { return redicect_url; }
	 * 
	 * public void setRedicect_url(String redicect_url) { this.redicect_url =
	 * redicect_url; }
	 * 
	 * public CacheManager getCacherManager() { return cacherManager; }
	 * 
	 * public void setCacherManager(CacheManager cacherManager) {
	 * this.cacherManager = cacherManager; }
	 */

}
